﻿// <copyright file="ColumnChartWindow.xaml.cs" company="Oleg V. Polikarpotchkin">
// Copyright © 2008-2009 Oleg V. Polikarpotchkin. All Right Reserved
// </copyright>
// <author>Oleg V. Polikarpotchkin</author>
// <email>ov-p@yandex.ru</email>
// <date>2009-03-10</date>
// <summary>OpenWPFChart library. ColumnChart Control use Sample.</summary>
// <revision>$Id$</revision>

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using Microsoft.Win32; // For OpenFileDialog and SaveFileDialog
using OpenWPFChart.Helpers;
using OpenWPFChart.Parts;

namespace ColumnChartControlSample
{
	/// <summary>
	/// ColumnChart Control use Sample main Window.
	/// </summary>
	public partial class ColumnChartWindow : Window
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="ColumnChartWindow"/> class.
		/// </summary>
		public ColumnChartWindow()
		{
			InitializeComponent();
		}

		/// <summary>
		/// Raises the <see cref="E:System.Windows.FrameworkElement.Initialized"/> event. 
		/// This method is invoked whenever <see cref="P:System.Windows.FrameworkElement.IsInitialized"/> 
		/// is set to true internally.
		/// </summary>
		/// <remarks>
		/// Loads the settings.
		/// </remarks>
		/// <param name="e">The <see cref="T:System.Windows.RoutedEventArgs"/> 
		/// that contains the event data.</param>
		protected override void OnInitialized(EventArgs e)
		{
			base.OnInitialized(e);

			// Load MRU files list
			ArrayList mru = Properties.Settings.Default.MRU;
			if (mru != null)
			{
				foreach (string fileName in mru)
				{
					MenuItem item = new MenuItem() { Header = fileName };
					item.Click += mru_Click;
					mnuRecentFiles.Items.Add(item);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="E:System.Windows.Window.Closed"/> event.
		/// </summary>
		/// <remarks>
		/// Saves the settings.
		/// </remarks>
		/// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
		protected override void OnClosed(EventArgs e)
		{
			base.OnClosed(e);

			// MRU files list.
			ArrayList mru = null;
			if (mnuRecentFiles.Items.Count > 0)
			{
				mru = new ArrayList();
				foreach (MenuItem item in mnuRecentFiles.Items)
				{
					mru.Add(item.Header as string);
				}
			}
			Properties.Settings.Default.MRU = mru;

			Properties.Settings.Default.Save();
		}

		#region Load Data
		/// <summary>
		/// Loads ItemData from the file.
		/// </summary>
		/// <param name="fileName">Name of the file.</param>
		/// <exception cref="Exception">Misc IO and file parser exceptions.</exception>
		private bool loadItemData(string fileName)
		{
			// Parse the Data File.
			string xAxisLabel = null;
			ObservableCollection<ItemData> dataCollection = null;
			FileInfo fi = new FileInfo(fileName);
			try
			{
				if (fi.Extension.ToLower() == ".txt")
					dataCollection = SpaceSeparatedDataFileParser.Parse(fileName, out xAxisLabel);
				else if (fi.Extension.ToLower() == ".xml")
					dataCollection = XMLDataFileParser.Parse(fileName, out xAxisLabel);

				if (dataCollection == null || dataCollection.Count == 0)
				{
					MessageBox.Show("Data wasn't loaded");
					return false;
				}
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.Message);
				return false;
			}

			// Clear Chart Data Source. It's required because otherwise an exception
			// on Chart Scales setting could be thrown.
			chart.ItemsSource = null;

			// Chart area size.
			Size areaSize = new Size(chart.ChartAreaRenderSize.Width > 0 ? chart.ChartAreaRenderSize.Width : ActualWidth
				, chart.ChartAreaRenderSize.Height > 0 ? chart.ChartAreaRenderSize.Height : ActualHeight);

			// Set the Chart control X,Y scales.
			try
			{
				setChartScales(dataCollection, areaSize, new string[] { "SampledCurveData`2" });
				if (chart.HorizontalScale == null || chart.VerticalScale == null)
				{
					MessageBox.Show(string.Format("There is no appropriate data in the {0} file", fileName)
						, "Load Data");
					return false;
				}
			}
			catch (ArgumentException ex)
			{
				MessageBox.Show(ex.Message);
				return false;
			}

			// PointMarker
			GeometryDrawing pointMarker = new GeometryDrawing(Brushes.Brown, new Pen(Brushes.Red, 1)
				, new EllipseGeometry(new Point(0, 0), 4, 4));
			pointMarker.Freeze();

			// Create DataViewCollection.
			ObservableCollection<DataView> dataViewCollection = new ObservableCollection<DataView>();
			ArrayExtension itemDecorations = FindResource("itemDecorations") as ArrayExtension;
			for (int i = 0; i < dataCollection.Count; ++i)
			{
				ItemData itemData = dataCollection[i];
				if (itemData.GetType().Name == "SampledCurveData`2")
				{ // 2D SampledCurveData
					ColumnChartItemDataView dataView = new ColumnChartItemDataView()
					{
						ItemData = itemData,
						HorizontalScale = chart.HorizontalScale,
						VerticalScale = chart.VerticalScale,
						PointMarker = pointMarker
					};

					// Set Default Item Decorations.
					int decIndex = i % itemDecorations.Items.Count;
					ItemDecorations decorations = itemDecorations.Items[decIndex] as ItemDecorations;
					decorations.CurvePen.Freeze();
					dataView.Pen = decorations.CurvePen;
					IPointMarker iPointMarker = dataView as IPointMarker;
					if (iPointMarker == null)
						continue;
					decorations.PointMarker.Freeze();
					iPointMarker.PointMarker = decorations.PointMarker;
					decorations.ColumnBrush.Freeze();
					dataView.ColumnBrush = decorations.ColumnBrush;

					dataViewCollection.Add(dataView);
				}
				// ScatteredPointsData<,> not supported
			}

			// Set Chart Data Source.
			chart.ItemsSource = dataViewCollection;
			// Set Horizontal Axis label.
			chart.HorizontalAxisTitle = xAxisLabel;

			// Reguery SwapLinLogScaleCommand
			((SwapLinLogScaleCommandImpl)SwapLinLogScaleCommand).NotifyChanged();

			return true;
		}

		/// <summary>
		/// Sets the Chart control X,Y scales.
		/// </summary>
		/// <param name="dataCollection">The data collection.</param>
		/// <param name="areaSize">Expected Chart area size.</param>
		/// <param name="allowedItemDataTypeNames">Item data type names filter. Can be <c>null</c>.</param>
		/// <exception cref="ArgumentException"/>
		void setChartScales(ObservableCollection<ItemData> dataCollection, Size areaSize
			, string[] allowedItemDataTypeNames)
		{
			// Retrieve the data to construct X,Y scales. Both dimensions must have the same type
			// and object-typed dimension must have the matching coordinate points sequences.
			object xMin = null, xMax = null, yMin = null, yMax = null;
			Type xType = null, yType = null;
			List<object> xSeries = null, ySeries = null;
			for (int i = 0; i < dataCollection.Count; ++i)
			{
				ItemData itemData = dataCollection[i];

				// Filter ItemData types.
				if (allowedItemDataTypeNames != null && allowedItemDataTypeNames.Length > 0)
				{
					if (!allowedItemDataTypeNames.Contains(itemData.GetType().Name))
						continue;
				}

				// Get Points enumerator.
				PropertyInfo piPoints = itemData.GetType().GetProperty("Points");
				if (piPoints == null)
					continue; // The item doesn't contain the points.
				var points = piPoints.GetValue(itemData, null) as IEnumerable;
				// The "points" should be IEnumerable<Point<>>. Get the Point<> type.
				Type[] argTypes = points.GetType().GetGenericArguments();
				Debug.Assert(argTypes != null, "argTypes != null");
				Debug.Assert(argTypes.Length == 1, "argTypes.Length == 1"
					, "Point enumerator type should have one argument.");
				Type pointType = argTypes[0];
				// Get types of Dimensions.
				argTypes = pointType.GetGenericArguments();
				Debug.Assert(argTypes != null, "argTypes != null");
				Debug.Assert(argTypes.Length > 1, "argTypes.Length > 1"
					, "Point type should have at least two arguments.");

				// Store and check X,Y dimensions types.
				if (xType == null)
					xType = argTypes[0];
				else if (xType != argTypes[0])
					throw new ArgumentException("Input file contains Point Series with mismatching abscissa base types.");
				if (yType == null)
					yType = argTypes[1];
				else if (yType != argTypes[1])
					throw new ArgumentException("Input file contains Point Series with mismatching ordinate base types.");

				// Abscissas.
				PropertyInfo piX = pointType.GetProperty("X");
				Debug.Assert(piX != null, "piX != null");
				var abscissas = from pt in points.Cast<object>() select piX.GetValue(pt, null);
				if (xType == typeof(object))
				{
					if (xSeries == null)
					{
						xSeries = abscissas.ToList();
						xMin = xSeries[0];
						xMax = xSeries[xSeries.Count - 1];
					}
					else if (!xSeries.SequenceEqual(abscissas))
						throw new ArgumentException("Abscissa Point Series mismatch.");
				}
				else if (xType.GetInterface("IComparable") != null)
				{
					var min = abscissas.Min();
					if (xMin == null)
						xMin = min;
					else if ((min as IComparable).CompareTo(xMin) < 0)
						xMin = min;

					var max = abscissas.Max();
					if (xMax == null)
						xMax = max;
					else if ((max as IComparable).CompareTo(xMax) > 0)
						xMax = max;
				}
				// Ordinates.
				PropertyInfo piY = pointType.GetProperty("Y");
				Debug.Assert(piY != null, "piY != null");
				var ordinates = from pt in points.Cast<object>() select piY.GetValue(pt, null);
				if (yType == typeof(object))
				{
					if (ySeries == null)
					{
						ySeries = ordinates.ToList();
						yMin = ySeries[0];
						yMax = ySeries[ySeries.Count - 1];
					}
					else if (!ySeries.SequenceEqual(ordinates))
						throw new ArgumentException("Ordinates Point Series mismatch.");
				}
				else if (yType.GetInterface("IComparable") != null)
				{
					var min = ordinates.Min();
					if (yMin == null)
						yMin = min;
					else if ((min as IComparable).CompareTo(yMin) < 0)
						yMin = min;

					var max = ordinates.Max();
					if (yMax == null)
						yMax = max;
					else if ((max as IComparable).CompareTo(yMax) > 0)
						yMax = max;
				}
			}
			Debug.Assert(xType != null && xMin != null && xMax != null, "xType != null && xMin != null && xMax != null");
			Debug.Assert((xType == typeof(object) && xSeries != null) || (xType != typeof(object) && xSeries == null)
				, "(xType == typeof(object) && xSeries != null) || (xType != typeof(object) && xSeries == null)");
			Debug.Assert(yType != null && yMin != null && yMax != null, "yType != null && yMin != null && yMax != null");
			Debug.Assert((yType == typeof(object) && ySeries != null) || (yType != typeof(object) && ySeries == null)
				, "(yType == typeof(object) && ySeries != null) || (yType != typeof(object) && ySeries == null)");

			// HorizontalScale
			if (xType == typeof(double))
				chart.HorizontalScale = new ChartLinearScale(xMin, xMax, areaSize.Width);
			else if (xType == typeof(DateTime))
				chart.HorizontalScale = new ChartDateTimeScale(xMin, xMax, areaSize.Width);
			else if (xType == typeof(object))
				chart.HorizontalScale = new ChartSeriesScale(xSeries, xMin, xMax, areaSize.Width);
			else
				throw new ArgumentException("Unsupported abscissa base type.");
			// Take side-by-side columns into account.
			chart.HorizontalScale.Start = chart.HorizontalScale.FromPixels(-dataCollection.Count * chart.ColumnWidth / 2);
			chart.HorizontalScale.Stop = chart.HorizontalScale.FromPixels(chart.HorizontalScale.ToPixels(xMax) + dataCollection.Count * chart.ColumnWidth / 2);

			// VerticalScale
			if (yType == typeof(double))
				chart.VerticalScale = new ChartLinearScale(yMax, yMin, areaSize.Height);
			else if (yType == typeof(DateTime))
				chart.VerticalScale = new ChartDateTimeScale(yMax, yMin, areaSize.Height);
			else if (yType == typeof(object))
				chart.VerticalScale = new ChartSeriesScale(ySeries, yMax, yMin, areaSize.Height);
			else
				throw new ArgumentException("Unsupported ordinate base type.");
		}
		/// <summary>
		/// Sets the Chart control X,Y scales.
		/// </summary>
		/// <param name="dataCollection">The data collection.</param>
		/// <param name="areaSize">Expected Chart area size.</param>
		/// <exception cref="ArgumentException"/>
		void setChartScales(ObservableCollection<ItemData> dataCollection, Size areaSize)
		{
			setChartScales(dataCollection, areaSize, null);
		}
		#endregion Load Data

		#region Swap Lin/Log Scale Command
		/// <summary>
		/// Command to Swap Linear/Logarithmic Axis.
		/// </summary>
		class SwapLinLogScaleCommandImpl : ICommand
		{
			ColumnChartWindow owner;

			/// <summary>
			/// Initializes a new instance of the <see cref="SwapLinLogScaleCommandImpl"/> class.
			/// </summary>
			/// <param name="owner">The owner.</param>
			public SwapLinLogScaleCommandImpl(ColumnChartWindow owner)
			{
				this.owner = owner;
			}

			#region ICommand inplementation
			/// <summary>
			/// Defines the method to be called when the command is invoked.
			/// </summary>
			/// <param name="parameter">string "Vertical" for the Vertical axis; whatever else for Horizontal axis.</param>
			public void Execute(object parameter)
			{
				bool isVertical = parameter != null && parameter is string
					&& (string)parameter == "Vertical";

				if (isVertical)
					owner.chart.VerticalScale = alternativeScale(owner.chart.VerticalScale, Extent.Height);
				else
					owner.chart.HorizontalScale = alternativeScale(owner.chart.HorizontalScale, Extent.Width);
			}

			/// <summary>
			/// Defines the method that determines whether the command can execute in its current state.
			/// </summary>
			/// <param name="parameter">string "Vertical" for the Vertical axis; whatever else for Horizontal axis.</param>
			/// <returns>
			/// <c>true</c> if this command can be executed; otherwise, <c>false</c>.
			/// </returns>
			public bool CanExecute(object parameter)
			{
				bool isVertical = parameter != null && parameter is string
					&& (string)parameter == "Vertical";

				ChartScale scale = null;
				double extent;
				if (isVertical)
				{
					scale = owner.chart.VerticalScale;
					extent = Extent.Height;
				}
				else
				{
					scale = owner.chart.HorizontalScale;
					extent = Extent.Width;
				}
				return alternativeScale(scale, extent) != null ? true : false;
			}

			public event EventHandler CanExecuteChanged;
			public void NotifyChanged()
			{
				if (CanExecuteChanged != null)
					CanExecuteChanged(this, EventArgs.Empty);
			}
			#endregion ICommand inplementation

			/// <summary>
			/// Gets given Scale alternative.
			/// </summary>
			/// <param name="scale">The scale.</param>
			/// <param name="extent">The extent.</param>
			/// <returns></returns>
			static ChartScale alternativeScale(ChartScale scale, double extent)
			{
				ChartScale newScale = null;
				if (scale is ChartLinearScale)
					newScale = new ChartLogarithmicScale(Convert.ToDouble(scale.Start)
						, Convert.ToDouble(scale.Stop), extent);
				else if (scale is ChartLogarithmicScale)
					newScale = new ChartLinearScale(Convert.ToDouble(scale.Start)
						, Convert.ToDouble(scale.Stop), extent);

				return (newScale != null && newScale.IsConsistent) ? newScale : null;
			}

			/// <summary>
			/// Gets the scale extent in pixels.
			/// </summary>
			Size Extent
			{
				get
				{
					return owner.chart.ChartAreaRenderSize.IsEmpty
						? owner.RenderSize : owner.chart.ChartAreaRenderSize;
				}
			}
		}

		ICommand swapScaleCommand;
		/// <summary>
		/// Gets the SwapLinLogScaleCommand.
		/// </summary>
		/// <value/>
		public ICommand SwapLinLogScaleCommand
		{
			get
			{
				if (swapScaleCommand == null)
					swapScaleCommand = new SwapLinLogScaleCommandImpl(this);
				return swapScaleCommand;
			}
		}
		#endregion Swap Lin/Log Scale Command

		#region OpenWPFChart.Item element events handlers
		/// <summary>
		/// Handles the MouseEnterItem event of the OpenWPFChart.Item element.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="OpenWPFChart.MouseItemEventArgs"/> 
		/// instance containing the event data.</param>
		private void chartItem_EnterItem(object sender, MouseItemEventArgs e)
		{
			Cursor = Cursors.Hand;
		}

		/// <summary>
		/// Handles the MouseLeaveItem event of the OpenWPFChart.Item element.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="OpenWPFChart.MouseItemEventArgs"/> 
		/// instance containing the event data.</param>
		private void chartItem_LeaveItem(object sender, MouseItemEventArgs e)
		{
			Cursor = null;
		}
		#endregion OpenWPFChart.Item element events handlers

		#region Menu actions handlers
		/// <summary>
		/// Handles the Click event of the MRU menu subitems.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
		private void mru_Click(Object sender, RoutedEventArgs e)
		{
			MenuItem item = sender as MenuItem;
			if (item == null)
				return;
			string fileName = item.Header as string;
			if (!loadItemData(fileName))
				// Remove from MRU list
				mnuRecentFiles.Items.Remove(item);
		}

		/// <summary>
		/// Handles the Click event of the AxisProperties menu items.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
		private void mnuViewAxisProperties_Click(object sender, RoutedEventArgs e)
		{
			bool horizontalAxis = true;
			if (sender == hAxisPropertiesMnuItem)
				horizontalAxis = true;
			else if (sender == vAxisPropertiesMnuItem)
				horizontalAxis = false;
			else
				return;
			Axis axis = null;
			if (horizontalAxis)
				axis = chart.HorizontalAxis;
			else
				axis = chart.VerticalAxis;

			AxisPropertiesDialog dlg = new AxisPropertiesDialog(axis) { Owner = this };
			if (dlg.ShowDialog() == true)
			{
				AxisDataView axisData = dlg.AxisData;
				// Set the Scale.
				if (horizontalAxis)
					chart.HorizontalScale = axisData.AxisScale;
				else
					chart.VerticalScale = axisData.AxisScale;

				UpdateLayout();
				if (horizontalAxis)
					axis = chart.HorizontalAxis;
				else
					axis = chart.VerticalAxis;
				axis.BeginInit();
				axis.ContentLayout = axisData.ContentLayout;
				axis.LabelFormat = axisData.LabelFormat;

				axis.TickLength = axisData.TickLength;
				axis.LongTickLength = axisData.LongTickLength;
				axis.LabelMargin = axisData.LabelMargin;
				axis.Pen = new Pen()
				{
					Brush = new SolidColorBrush(axisData.PenColor),
					Thickness = axisData.PenThickness
				};
				axis.FontFamily = axisData.FontFamily;
				axis.FontSize = axisData.FontSize;
				axis.FontStretch = axisData.FontStretch;
				axis.FontStyle = axisData.FontStyle;
				axis.FontWeight = axisData.FontWeight;
				axis.EndInit();
			}
		}

		/// <summary>
		/// Handles the Click event of the Exit control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
		private void Exit_Click(object sender, RoutedEventArgs e)
		{
			Close();
		}
		#endregion Menu actions handlers

		#region Command Handlers
		/// <summary>
		/// Execute <see cref="ApplicationCommands.Open"/> command.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
		private void OpenCommandHandler(object sender, ExecutedRoutedEventArgs e)
		{
			OpenFileDialog dlg = new OpenFileDialog();
			dlg.Filter = "Text Files (*.txt)|*.txt|XML Files (*.xml)|*.xml|All Files (*.*)|*.*";
			if (dlg.ShowDialog() == true)
			{
				if (loadItemData(dlg.FileName))
				{
					// Add to MRU list
					foreach (MenuItem item in mnuRecentFiles.Items)
					{
						if (dlg.FileName == item.Header as string)
							return;
					}

					MenuItem newItem = new MenuItem() { Header = dlg.FileName };
					newItem.Click += mru_Click;
					mnuRecentFiles.Items.Add(newItem);
				}
			}
		}

		#region Properties command handlers
		/// <summary>
		/// Executes <see cref="ApplicationCommands.Properties"/> command.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
		void PropertiesExecutedCommandHandler(object sender, ExecutedRoutedEventArgs e)
		{
			ContextMenu mnu = (ContextMenu)FindResource("chartItemCtxMnu");
			Debug.Assert(mnu != null, "mnu != null");
			ListBoxItem listBoxItem = e.OriginalSource as ListBoxItem;
			Debug.Assert(listBoxItem != null, "listBoxItem != null");
			ItemDataView itemDataView = listBoxItem.DataContext as ItemDataView;
			Debug.Assert(itemDataView != null, "itemDataView != null");

			if (mnu.Tag != null)
			{ // Show ItemVisual Properties dialog.
				ItemPropertiesDialog dlg = new ItemPropertiesDialog(itemDataView.Clone() as ItemDataView) { Owner = this };
				if (dlg.ShowDialog() == true)
				{
					if (chart.ItemsSource != null)
					{ // Replace chart.ItemsSource.
						ObservableCollection<DataView> coll = new ObservableCollection<DataView>(
							chart.ItemsSource.Cast<DataView>());
						// Replace ItemDataView changed.
						int index = coll.IndexOf(itemDataView);
						Debug.Assert(index >= 0, "index >= 0");
						coll[index] = dlg.ItemDataView;

						chart.ItemsSource = null;
						chart.HorizontalScale = null;
						chart.VerticalScale = null;
						chart.ItemsSource = coll;
					}
					else
					{ // chart.ItemsSource is null.
						// Replace ItemDataView changed.
						int index = chart.Items.IndexOf(itemDataView);
						Debug.Assert(index >= 0, "index >= 0");
						chart.Items[index] = dlg.ItemDataView;
						chart.HorizontalScale = dlg.ItemDataView.HorizontalScale;
						chart.VerticalScale = dlg.ItemDataView.VerticalScale;
					}
				}
				e.Handled = true;
			}
		}

		/// <summary>
		/// Checks the <see cref="ApplicationCommands.Properties"/> command can be executed.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
		private void PropertiesCanExecuteCommandHandler(object sender, CanExecuteRoutedEventArgs e)
		{
			ListBoxItem listBoxItem = e.OriginalSource as ListBoxItem;
			if (listBoxItem != null)
			{
				ItemDataView itemDataView = listBoxItem.DataContext as ItemDataView;
				if (itemDataView != null)
				{
					e.CanExecute = true;
					return;
				}
			}
			e.CanExecute = false;
		}
		#endregion Properties command handlers

		/// <summary>
		/// Execute <see cref="NavigationCommands.Zoom"/> command.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
		private void ZoomCommandHandler(object sender, ExecutedRoutedEventArgs e)
		{
			double zoom = 1.2;
			if (e.Parameter != null)
			{
				try
				{
					zoom = Convert.ToDouble(e.Parameter);
				}
				catch (Exception ex)
				{
					Trace.WriteLine("ZoomCommandHandler " + ex.Message);
					return;
				}
			}

			if (zoom == 0.0)
				return;
			if (zoom < 0.0)
				zoom = -1.0 / zoom;

			chart.HorizontalScale.Scale *= zoom;
			chart.VerticalScale.Scale *= zoom;
		}
		#endregion Command Handlers
	}

	/// <summary>
	/// Item Decorations
	/// </summary>
	[Serializable]
	public class ItemDecorations
	{
		public Pen CurvePen { get; set; }
		public Drawing PointMarker { get; set; }
		public Brush ColumnBrush { get; set; }
	}
}
